const mongoose = require('mongoose');
const RuntimeCheck = require('./mongo/runtime-check');
const Function = require('./mongo/function');
const File = require('./mongo/file');
const utility = require('./utility');
const fileElementsModels = require('../models/file-elements');
const fileModels = require('../models/files');
const tasks = require('../models/tasks');
const PhoenixError = require('../models/phoenix-error');

/**
 *  运行时规则
 * @type {Object}
 */
const runTimeCheckRules = {
  Overflow: 1, // 数据类型溢出
  'Out of array bound': 2, // 数组访问越界
  'Divide zero': 3, // 除零异常
  'Unreachable code': 4, // 死代码
  'Mismatching declaration': 5, // 声明不匹配
  'Conflicting shared variable': 6, // 共享变量冲突
  'Invalid shift operation': 7, // 无效移位操作
  'Illegally dereferenced pointer': 8, // 非法指针解引用
};
async function getRuntimeCheckResult(versionId, ruleId) {
  versionId = mongoose.Types.ObjectId(versionId);
  const filter = { versionId };
  if (ruleId && !utility.isArrayType(ruleId)) {
    ruleId = [ruleId];
  }
  if (ruleId) {
    filter.rule = { $in: ruleId.filter((i) => i).map((i) => parseInt(i, 10)) };
  }
  const runtimeCheckResults = await RuntimeCheck.find(filter).lean();
  const obj = runtimeCheckResults.reduce((obj, item) => {
    const key = item.fileId.toString();
    obj[key] = obj[key] || { result: [], fileId: item.fileId, filePath: item.filePath };
    obj[key].result.push(item);
    return obj;
  }, {});
  return [...Object.values(obj)];
}
/**
 * 运行时检查
 *
 * @param {Array[String]} extnames  源码文件后缀名
 * @param {Object} project 项目
 * @param {Object} projectVersion 版本
 * @param {Object} creator 创建用户
 * @param {Array[numner]} ruleIds 运行时检查规则id
 */
async function runTimeCheck(extnames, project, projectVersion, creator, ruleIds = Object.values(runTimeCheckRules)) {
  const files = await File.find({
    versionId: projectVersion._id,
    kind: fileModels.kind.file,
    status: fileModels.statusKind.compiled,
    extname: { $in: extnames },
  }, ['_id', 'path']).lean();
  if (files.length === 0) {
    throw new PhoenixError('No files were parsed');
  }
  const funcs = await Function.find({
    fileId: { $in: files.map((a) => a._id) },
    fanIn: 0,
    kind: { $in: fileElementsModels.getCountableKinds() },
  },
  ['_id', 'functionName', 'fileId']).lean();
  if (funcs.length === 0) {
    throw new PhoenixError('no functions that have not been called');
  }
  const fileFuncs = {};
  files.forEach((f) => {
    const filePath = f.path;
    if (!fileFuncs[filePath]) {
      fileFuncs[filePath] = [];
    }
    funcs.forEach((func) => {
      if (func.fileId.toString() === f._id.toString()) {
        fileFuncs[filePath].push(func.functionName);
      }
    });
  });
  await RuntimeCheck.deleteMany({ versionId: projectVersion._id });
  return tasks.addRunTimeCheckTask(files, fileFuncs, project, projectVersion, creator, ruleIds);
}

module.exports = {
  getRuntimeCheckResult,
  runTimeCheck,
  runTimeCheckRules,
};
exports = Object.assign(exports, module.exports);
